package com.gitee.wsl.mathematics.geometry.d2.contour.ext

import com.gitee.wsl.mathematics.geometry.d2.contour.ContourShape
import com.gitee.wsl.mathematics.geometry.d2.contour.ShapeContour
import com.gitee.wsl.mathematics.geometry.d2.contour.segment.perpendicular
import com.gitee.wsl.mathematics.geometry.d2.contour.shape
import com.gitee.wsl.mathematics.geometry.d2.line.Line
import com.gitee.wsl.mathematics.geometry.d2.path.splitAtBase
import kotlin.jvm.JvmName
import kotlin.math.abs

/** Splits a [ShapeContour] with another [ShapeContour]. */
fun ShapeContour.split(cutter: ShapeContour) = split(this, cutter)

/** Splits a [ShapeContour] with a [List] of [ShapeContour]s. */
fun ShapeContour.split(cutters: List<ShapeContour>) = split(this, cutters)

/**
 * Splits a [ContourShape] into two separate [ContourShape]s from given [LineSegment].
 *
 * [LineSegment] doesn't necessarily need to cover the full length of the [ContourShape],
 * as it will be extended on both ends to ensure it splits the whole [ContourShape].
 *
 * @return A pair containing two partial [ContourShape]s.
 */
@JvmName("splitLine")
fun split(shape: ContourShape, cutter: Line): Pair<ContourShape, ContourShape> {
    val center = (cutter.end + cutter.start) / 2.0
    val direction = (cutter.end - cutter.start).normalized
    val perpendicular = direction.perpendicular(shape.contours.first().polarity)
    val extend = 50000.0

    val splitLine = Line(center - direction * extend, center + direction * extend)

    val leftContour = shape {
        contour {
            moveTo(splitLine.start)
            lineTo(cursor + perpendicular * extend)
            lineTo(cursor + direction * extend)
            lineTo(splitLine.end)
            lineTo(splitLine.start)
            close()
        }
    }

    val rightContour = shape {
        contour {
            moveTo(splitLine.start)
            lineTo(cursor - perpendicular * extend)
            lineTo(cursor + direction * extend)
            lineTo(splitLine.end)
            lineTo(splitLine.start)
            close()
        }
    }
    val leftShape = difference(shape, leftContour)
    val rightShape = difference(shape, rightContour)
    return Pair(leftShape, rightShape)
}

/**
 * Splits a [ShapeContour] with another [ShapeContour].
 *
 * If there is no intersection, the original contour is returned.
 */
@JvmName("splitShapeContour")
fun split(from: ShapeContour, cutter: ShapeContour): List<ShapeContour> {
    if (from.empty) return listOf()
    if (cutter.empty) return listOf(from)

    val ints = intersections(from, cutter)
    return performSplit(from, ints)
}

/**
 * Splits a [ShapeContour] with all other [ShapeContour] in a [List].
 */
@JvmName("splitShapeContourList")
fun split(from: ShapeContour, cutters: List<ShapeContour>): List<ShapeContour> {
    if (from.empty) return listOf()

    // Do `it != from` because we may want to split all org.openrndr.shape.contours
    // in a collection at their intersections.
    // We would iterate over each element and cut against
    // "all other items". Building a collection with "all other items"
    // for each item is tedious. Easier to just discard if equal.
    val validCutters = cutters.filter { !it.empty && it != from }
    if (validCutters.isEmpty()) return listOf(from)

    val ints = validCutters.map { cutter ->
        intersections(from, cutter)
    }.flatten()

    return performSplit(from, ints)
}

/**
 * Performs the actual ShapeContour cutting. Receive the shape to be cut
 * and a list of all the cut points.
 */
private fun performSplit(from: ShapeContour, ints: List<ContourIntersection>):
        List<ShapeContour> {
    return if (ints.isNotEmpty()) {
        val sortedInts = ints.map { it.a.contourT }.sorted()
        val weldedInts = (if (from.closed) {
            sortedInts + (if (sortedInts.first() > 0.0) 1 +
                    sortedInts.first() else null)
        } else {
            listOf(if (sortedInts.first() > 0.0) 0.0 else null) +
                    sortedInts + (if (sortedInts.last() < 1.0) 1.0 else null)
        }).filterNotNull().merge { a, b -> abs(a - b) < 1E-6 }
        weldedInts.zipWithNext().map { from.sub(it.first, it.second) }
    } else {
        listOf(from)
    }
}

fun ShapeContour.splitAt(segmentIndex: Double, segmentT: Double): List<ShapeContour> {
    val t = (1.0 / segments.size) * (segmentIndex + segmentT)
    return splitAt(listOf(t))
}


fun ShapeContour.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<ShapeContour> {
    @Suppress("UNCHECKED_CAST")
    return splitAtBase(ascendingTs, weldEpsilon) as List<ShapeContour>
}